//
// Created by zhouming on 2021/9/13.
//

#include "invoke_java.h"

#include "plog.h"

JNIEnv *envPtr;
jobject ctx;

void SetEnv(JNIEnv *env, jobject context) {
    envPtr = env;
    ctx = context;
}

static inline jclass findClass(char* name)
{
    jobject g_cls_string;
    jclass clazz = envPtr->FindClass(name);
    if(clazz)
    {
        g_cls_string = envPtr->NewGlobalRef(clazz);
        return static_cast<jclass>(g_cls_string);
    }
    else
    {
        return 0;
    }
}

static inline jvalue CallObjectMethodV(jobject object, char *methodName, char *paramCode, ...) {
    jclass cls = envPtr->GetObjectClass(object);
    jmethodID methodId = envPtr->GetMethodID(cls, methodName, paramCode);

    char *p = paramCode;

    // skip '()' to find out the return type
    while (*p != ')') {
        p++;
    }
    // skip ')'
    p++;

    va_list args;
    va_start(args, paramCode);
    jvalue value;

    switch (*p) {
        case 'V':
            envPtr->CallVoidMethodV(object, methodId, args);
            break;

        case '[':
        case 'L':
            value.l = envPtr->CallObjectMethodV(object, methodId, args);
            break;

        case 'Z':
            value.z = envPtr->CallBooleanMethodV(object, methodId, args);
            break;

        case 'B':
            value.b = envPtr->CallByteMethodV(object, methodId, args);
            break;

        case 'C':
            value.c = envPtr->CallCharMethodV(object, methodId, args);
            break;

        case 'S':
            value.s = envPtr->CallShortMethodV(object, methodId, args);
            break;

        case 'I':
            value.i = envPtr->CallIntMethodV(object, methodId, args);
            break;

        case 'J':
            value.j = envPtr->CallLongMethodV(object, methodId, args);
            break;

        case 'F':
            value.f = envPtr->CallFloatMethodV(object, methodId, args);
            break;

        case 'D':
            value.d = envPtr->CallDoubleMethodV(object, methodId, args);
            break;

        default:
            LOGE("JniTool CallObjectMethod paramCode = %s, illegal", paramCode);
    }

    va_end(args);

    return value;
}

static inline jvalue CallClassMethodV(jclass cls, char *methodName, char *paramCode, va_list args) {
    jmethodID methodId = envPtr->GetStaticMethodID(cls, methodName, paramCode);

    char *p = paramCode;

    // skip '()' to find out the return type
    while (*p != ')') {
        p++;
    }
    // skip ')'
    p++;

    jvalue value;
    switch (*p) {
        case 'V':
            envPtr->CallStaticVoidMethodV(cls, methodId, args);
            break;

        case '[':
        case 'L':
            value.l = envPtr->CallStaticObjectMethodV(cls, methodId, args);
            break;

        case 'Z':
            value.z = envPtr->CallStaticBooleanMethodV(cls, methodId, args);
            break;

        case 'B':
            value.b = envPtr->CallStaticByteMethodV(cls, methodId, args);
            break;

        case 'C':
            value.c = envPtr->CallStaticCharMethodV(cls, methodId, args);
            break;

        case 'S':
            value.s = envPtr->CallStaticShortMethodV(cls, methodId, args);
            break;

        case 'I':
            value.i = envPtr->CallStaticIntMethodV(cls, methodId, args);
            break;

        case 'J':
            value.j = envPtr->CallStaticLongMethodV(cls, methodId, args);
            break;

        case 'F':
            value.f = envPtr->CallStaticFloatMethodV(cls, methodId, args);
            break;

        case 'D':
            value.d = envPtr->CallStaticDoubleMethodV(cls, methodId, args);
            break;

        default:
            LOGE("JniTool CallClassMethod paramCode = %s, illegal", paramCode);
    }

    return value;
}

jvalue CallObjectMethod(jobject cls, char *methodName, char *paramCode, ...) {
    va_list args;
    va_start(args, paramCode);
    jvalue value = CallObjectMethodV(cls, methodName, paramCode, args);
    va_end(args);

    return value;
}


jvalue CallStaticMethod(char *className, char *methodName, char *paramCode, ...) {
    va_list args;
    va_start(args, paramCode);
    jvalue value = CallClassMethodV(findClass(className), methodName, paramCode, args);
    va_end(args);

    return value;
}

jvalue GetStaticField(char *className, char *fieldName, char *paramCode) {
    jclass cls = findClass(className);
    jfieldID fieldId = envPtr->GetStaticFieldID(cls, fieldName, paramCode);

    char *p = paramCode;

    jvalue value;
    switch (*p) {
        case '[':
        case 'L':
            value.l = envPtr->GetStaticObjectField(cls, fieldId);
            break;

        case 'Z':
            value.z = envPtr->GetStaticBooleanField(cls, fieldId);
            break;

        case 'B':
            value.b = envPtr->GetStaticByteField(cls, fieldId);
            break;

        case 'C':
            value.c = envPtr->GetStaticCharField(cls, fieldId);
            break;

        case 'S':
            value.s = envPtr->GetStaticShortField(cls, fieldId);
            break;

        case 'I':
            value.i = envPtr->GetStaticIntField(cls, fieldId);
            break;

        case 'J':
            value.j = envPtr->GetStaticLongField(cls, fieldId);
            break;

        case 'F':
            value.f = envPtr->GetStaticFloatField(cls, fieldId);
            break;

        case 'D':
            value.d = envPtr->GetStaticDoubleField(cls, fieldId);
            break;

        default:
            LOGE("JniTool CallClassMethod paramCode = %s, illegal", paramCode);
    }

    return value;
}